﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using ColladaDotNet.Geometry;
using Microsoft.DirectX;

namespace BakalarskaPraca.Generation.Data
{
    public class HumidityPolygon : Polygon
    {
        //protected Bitmap HumidityMapMask;
        protected byte[][] HumidityMapValues;
        protected bool[][] HumidityMaskValues;
        protected Matrix TransformMatrix;
        protected Matrix InversedTransformMatrix;
        protected Plane Plane;

        protected int Width;
        protected int Height;

        protected float OffsetX;
        protected float OffsetY;
        protected float ScaleFactor;
        
        protected HumidityPolygon(){}

        public HumidityPolygon(Polygon polygon)
        {
            this._vertices = polygon.Vertices;
            this.ScaleFactor = Properties.Settings.Default.PlantSimulationScaleFactor;

            Vector3[] vectors = this.Vertices.Select(v => v.Positions[0].Vector).ToArray();
            this.Plane = Plane.FromPoints(vectors[0], vectors[1], vectors[2]);
            this.TransformMatrix = this.GenerateTransformMatrix(polygon.Normal);
            this.InversedTransformMatrix = this.GenerateTransformMatrix(polygon.Normal);
            this.InversedTransformMatrix.Invert();
            this.CreateHumidityMapMask();
            this.Width = this.HumidityMaskValues.Count();
            this.Height = this.HumidityMaskValues[0].Count();
            this.SetHumidityLevel(0);
        }

        private Matrix GenerateTransformMatrix(Vector3 normal)
        {
            Matrix transformMatrix = Matrix.Identity;
            float lengthXZ = (float)(Math.Sqrt(normal.X * normal.X + normal.Z * normal.Z));
            float angleY = (float)Math.Asin(normal.X / lengthXZ);

            transformMatrix.Multiply(Matrix.RotationY(-Math.Sign(normal.Z) * angleY));
            normal.TransformCoordinate(transformMatrix);

            float lengthYZ = (float)(Math.Sqrt(normal.Y * normal.Y + normal.Z * normal.Z));
            float angleX = (float)Math.Asin(normal.Y / lengthYZ);

            transformMatrix.Multiply(Matrix.RotationX(Math.Sign(normal.Z) * angleX));

            return transformMatrix;
        }

        private unsafe void CreateHumidityMapMask()
        {
            Vector3[] vectors = this.Vertices.Select(v => v.Positions[0].Vector).ToArray();

            for (int i = 0; i < vectors.Count(); i++)
            {
                vectors[i].TransformCoordinate(this.TransformMatrix);
            }

            float minX = vectors.Min(v => v.X);
            float minY = vectors.Min(v => v.Y);

            this.OffsetX = -minX;
            this.OffsetY = -minY;

            int width = (int)Math.Round((vectors.Max(v => v.X) + this.OffsetX) * this.ScaleFactor) + 1;
            int height = (int)Math.Round((vectors.Max(v => v.Y) + this.OffsetY) * this.ScaleFactor) + 1;

            Bitmap mapMask = new Bitmap(width, height);

            PointF[] points = vectors.Select(v =>
            {
                float x = (v.X + this.OffsetX) * ScaleFactor;
                float y = (v.Y + this.OffsetY) * ScaleFactor;

                return new PointF(x, y);
            }).ToArray();

            Graphics g = Graphics.FromImage(mapMask);

            g.Clear(Color.Black);
            g.FillPolygon(Brushes.White, points);

            BitmapData maskData = mapMask.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
                                       mapMask.PixelFormat);

            const int maskPixelSize = 4;

            IntPtr maskHandle = maskData.Scan0;

            this.HumidityMaskValues = new bool[width][];
            for (int x = 0; x < width; x++)
            {
                this.HumidityMaskValues[x] = new bool[height];
            }
            
            for (int y = 0; y < height; y++)
            { 
                byte* maskDataRow = (byte*)maskHandle + (y * maskData.Stride);
                for (int x = 0; x < width; x++)
                {
                    this.HumidityMaskValues[x][y] = (maskDataRow[x * maskPixelSize] > 0);
                }
            }
        }

        public int GetHumidity(float x, float y)
        {
            int pX = (int)Math.Round((x + this.OffsetX) * this.ScaleFactor);
            int pY = (int)Math.Round((y + this.OffsetY) * this.ScaleFactor);

            if (pX >= 0 && pY >= 0 && pX < this.Width && pY < this.Height)
            {
                if (this.HumidityMaskValues[pX][pY])
                    return this.HumidityMapValues[pX][pY];
            }
            return -1;
        }

        public void SetHumidityLevel(byte humidityLevel)
        {
            this.HumidityMapValues = new byte[this.Width][];
            for(int x = 0; x < this.Width; x++)
            {
                this.HumidityMapValues[x] = new byte[this.Height];
                for(int y = 0; y < this.Height; y++)
                {
                    this.HumidityMapValues[x][y] = humidityLevel;
                }
            }
        }

        public float GetZPoisiton(float x, float y)
        {
            return (this.Plane.A * x + this.Plane.B * y + this.Plane.D) / -this.Plane.C;
        }

        protected void CopyFrom(HumidityPolygon polygon)
        {
            this._vertices = polygon._vertices;
            this.HumidityMaskValues = polygon.HumidityMaskValues;
            this.Width = polygon.Width;
            this.Height = polygon.Height;
            this.HumidityMapValues = polygon.HumidityMapValues;
            this.InversedTransformMatrix = polygon.InversedTransformMatrix;
            this.OffsetX = polygon.OffsetX;
            this.OffsetY = polygon.OffsetY;
            this.Plane = polygon.Plane;
            this.ScaleFactor = polygon.ScaleFactor;
            this.TransformMatrix = polygon.TransformMatrix;
        }

        public int GetHumidity(PointF position)
        {
            return this.GetHumidity(position.X, position.Y);
        }
    }
}
